/**
    Implementation of synchronization primitives.
    To be compiled with:
     - -D_MWAIT if you want to use the implementation that uses the instructions monitor/mwait
     - otherwise will be used the implementation that uses spinlock as synchronization mechanism

     @author Tiziano De Matteis
*/
#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
#include <sys/time.h>
#include <stdlib.h>
#include "../Include/synchr.h"
#ifdef _MWAIT
    #include <pmmintrin.h>
#endif


/*
    Initialization of a lock variable
*/
inline void initLockVariable(lock_variable_t *l)
{
    *l=0;
}
/*
   Spin lock procedure
*/
inline void spin_lock(lock_variable_t *lV)
{
    //if equal to 1 the variable is locked, if equal to 0 is unlocked

    asm volatile ( "\n"
                   "Spin_Lock:\n"
                   "\tmovl %1, %%eax\n"
                   "\tCMPL $0, %%eax\n"
                   "\tJE Get_Lock\n"
                   "\tPAUSE\n"
                   "\tJMP Spin_Lock\n"
                   "Get_Lock:\n"
                   "\tMOV $1, %%eax\n"
#ifdef NO_XCGH
                   "\tMOV %%eax, %0\n" /* lockVariable -> eax register */
                   "\tMOV $0, %%eax\n" /* Assign 0 to the register */
#else
                   "\tXCHG %0, %%eax\n" /* Exchange (atomic) the content of lockVariable with EAX */
#endif
                   "\tCMP $0, %%eax\n"
                   "\tJNE Spin_Lock\n"
               :"+m"(*lV)
                       :"m"(*lV)
                       :"eax"
                     );

}

/*
   Spin unlock procedure
*/

inline void spin_unlock(lock_variable_t *lV)
{
    *lV=0; //reset the lock variable
}

/* Implementation using the monitor/mwait instructions*/
#ifdef _MWAIT
/*
    To notify we access to the condition variable  and we set
    the value of the synchronization variable to SYNCHR_VALUE
    if someone is waiting
    Must be called in a critical section.
*/
inline void notify(cond_variable_t *c)
{
    if(*c[0]==UNSYNCHR_VALUE) //the partner is waiting
        *c[0]=SYNCHR_VALUE; //update the value
}

/*
    Initialization of a condition variable in the case that spin locks are used
    for synchronization.
*/
inline void initCondVariable(cond_variable_t *c)
{
    *c[0]=UNSYNCHR_VALUE;
}

/*
    Returns true if the partner has already notified
*/
inline bool hasAlreadyNotified(cond_variable_t *c)
{
    return *c[0]==SYNCHR_VALUE;
}

/*
    To wait for the notification by the partner, we will use the monitor/mwait
    istructions offered by Intel.
    - we reset the synchronization variable
    - we set as monitor region the synchronization variable, unlock the condition variable
        and execute the mwait instruction. Every time that the thread is waked-up, we access again
        to the condition variable for checking the value of synchronization variable (we have to avoid
        false wake-up)
    To be called in a critical section.
*/
inline void waitNotify(cond_variable_t *c, lock_variable_t *l)
{

    bool notified=false;
    *c[0]=UNSYNCHR_VALUE;
    do //monitor/mwait cycle
    {
        _mm_monitor(c,0,0); //set the monitor region
        spin_unlock(l);

        _mm_mwait(0,0); //wait for a store on it

        spin_lock(l); //access to the condition variable and check
        if(*c[0]==SYNCHR_VALUE) //the partner notified
            notified=true;
        //otherwise loop

    }while(!notified);


}

#else /*Implementation using the traditional spin locks*/

/*
    To notify we access to the condition variable  and we set
    the value of the synchronization variable to SYNCHR_VALUE
    if someone is waiting
    Must be called in a critical section.
*/
inline void notify(cond_variable_t *c)
{
    if(*c==UNSYNCHR_VALUE) //the partner is waiting
        *c=SYNCHR_VALUE; //update the value
}

/*
    Initialization of a condition variable in the case that spin locks are used
    for synchronization.
*/
inline void initCondVariable(cond_variable_t *c)
{
    *c=UNSYNCHR_VALUE;
}

/*
    Returns true if the partner has already notified
*/
inline bool hasAlreadyNotified(cond_variable_t *c)
{
    return *c==SYNCHR_VALUE;
}

/*
    Ciclically we access to the condition variable in order
    to check the synchronization variable.
    To be called in a critical section.
*/
inline void waitNotify(cond_variable_t *c, lock_variable_t *l)
{
    bool notified=false;
    *c=UNSYNCHR_VALUE;
    spin_unlock(l); //free the lock variable
    do
    {
        spin_lock(l); //lock

        if(*c==SYNCHR_VALUE) //the partner notified
            notified=true;
        else
            spin_unlock(l); //unlock and loop
    }while(!notified);

}

#endif
